import Stage from '.';
import { dist, getOffsetCoord, isNodeColliding, isNodeInRegionRect } from '@/utils';
import {
  handleHover,
  handleSelect,
  updateCursor,
  handleCopy,
  handleCut,
  handlePaste,
  handleHotkey,
  generateGuides,
} from './handle';

/**
 * 组装事件对象
 * @param {Event} e
 * @param {object} target 事件的主要目标
 * @param {object} currentTarget 当前节点
 * @returns
 */
const evtFill = (e, target, currentTarget) => ({
  e,
  target,
  currentTarget,
  cancelBubble: false,
  stopPropagation() {
    this.cancelBubble = true;
  },
});

/**
 * 统一事件分发
 * @param {string} eventName
 * @param {Event} e
 * @param {object} evt
 * @returns
 * @this {Stage}
 */
function evtEmit(eventName, e, evt) {
  const hoverNode = this.hoverNode || this;
  if (!evt) evt = evtFill(e, hoverNode, this);
  if (hoverNode !== this) {
    evt.target = hoverNode;
    evtEmit.call(hoverNode, eventName, e, evt);
    if (evt.cancelBubble) return;
  }
  evt.currentTarget = this;
  this.emit(eventName, evt);
}

/**
 * 客户端鼠标移动事件
 * @param {MouseEvent} e
 * @this {Stage}
 */
export function onWindowMousemove(e) {
  this.emit('window.mousemove', e);
  // 更新鼠标相对画布坐标（画布相对坐标）
  this.offsetCoord = getOffsetCoord(this.canvas.el, e);
  // 处理 hover 事件
  handleHover.call(this, e);
  // 更新鼠标指针
  updateCursor.call(this, e);
  // 重绘
  this.draw();
}

/**
 * 客户端拖动中事件
 * @param {DragEvent} e
 * @this {Stage}
 */
export function onWindowDragover(e) {
  this.emit('window.dragover', e);
  // 更新鼠标相对画布坐标（画布相对坐标）
  this.offsetCoord = getOffsetCoord(this.canvas.el, e);
}

/**
 * 客户端鼠标弹起事件
 * @param {MouseEvent} e
 * @this {Stage}
 */
export function onWindowMouseup(e) {
  this.emit('window.mouseup', e);
  // 在浏览器下次重绘之前 重绘画布
  requestAnimationFrame(() => this.draw());
}

/**
 * 客户端键盘按下事件
 * @this {Stage}
 */
export async function onWindowKeydown(e) {
  this.emit('window.keydown', e);
  // 触发目标为输入框 阻断
  if (['INPUT', 'TEXTAREA', 'SELECT'].includes(e.target?.tagName)) return;
  // 记录已按下键值
  this.pressedKeys.add(e.key);
  // 更新鼠标指针
  updateCursor.call(this, e);
  // 触发快捷键事件
  handleHotkey.call(this, e);
}

/**
 * 客户端键盘释放事件
 * @this {Stage}
 */
export async function onWindowKeyup(e) {
  this.emit('window.keyup', e);
  // 触发目标为输入框 阻断
  if (['INPUT', 'TEXTAREA', 'SELECT'].includes(e.target?.tagName)) return;
  // 移除已按下键值
  this.pressedKeys.delete(e.key);
  // 更新鼠标指针
  updateCursor.call(this, e);
}

/**
 * 客户端复制事件
 * @this {Stage}
 */
export async function onCopy(e) {
  this.emit('window.copy', e);
  // 触发目标为输入框 阻断
  if (['INPUT', 'TEXTAREA', 'SELECT'].includes(e.target?.tagName)) return;
  if (e.target !== document.body) return;
  e.preventDefault();
  // 触发复制
  handleCopy.call(this, e);
}

/**
 * 客户端剪切事件
 * @this {Stage}
 */
export async function onCut(e) {
  this.emit('window.cut', e);
  // 触发目标为输入框 阻断
  if (['INPUT', 'TEXTAREA', 'SELECT'].includes(e.target?.tagName)) return;
  if (e.target !== document.body) return;
  e.preventDefault();
  // 触发剪切
  handleCut.call(this, e);
}

/**
 * 客户端粘贴事件
 * @this {Stage}
 */
export async function onPaste(e) {
  this.emit('window.paste', e);
  // 触发目标为输入框 阻断
  if (['INPUT', 'TEXTAREA', 'SELECT'].includes(e.target?.tagName)) return;
  // 触发粘贴
  handlePaste.call(this, e);
}

/**
 * 鼠标移动事件
 * @this {Stage}
 */
export function onMousemove(e) {
  evtEmit.call(this, 'mousemove', e);
}

/**
 * 鼠标按下事件
 * @param {MouseEvent} e
 * @this {Stage}
 */
export function onMousedown(e) {
  // 更新鼠标相对画布坐标（画布相对坐标）
  this.offsetCoord = getOffsetCoord(this.canvas.el, e);
  window.getSelection()?.empty(); // 清除已选文本
  evtEmit.call(this, 'mousedown', e);
  const { draggable, selectable, dragging, pressedKeys } = this;
  // 左键
  if (e.button === 0) {
    // 触发滚动条事件
    if (this.hoveringScrollbar) onScrollbarStart.call(this, e);
    // 画布可拖动 且（不可选择节点 或 空格键按下）触发画布拖动
    else if (draggable && (!selectable || pressedKeys.has(' '))) onStageDragStart.call(this, e);
    // 可选择节点 且 非画布拖动中状态
    else if (selectable && !dragging) {
      const { gridCoord, transformer } = this;
      let isDefault = false;
      const transformAction = transformer.pointAction(gridCoord);
      // shift键按下
      if (e.shiftKey) isDefault = true;
      // 转换器事件
      else if (transformAction) {
        // 转换器拖动事件，触发节点拖动
        if (transformAction === 'drag') onNodeDragStart.call(this, e);
        // 触发转换器内部事件
        else transformer.action(transformAction, e);
      }
      // 触发锚点连线事件
      else if (!this.hoveringEdge && this.hoveringAnchor) onEdgeStart.call(this, e);
      else isDefault = true;
      // 默认 触发选择事件
      if (isDefault) handleSelect.call(this, e);
    }
  }
  // 中键 且 画布可拖动 触发画布拖动
  else if (e.button === 1 && draggable) {
    e.preventDefault();
    onStageDragStart.call(this, e);
  }
}

/**
 * 鼠标弹起事件
 * @this {Stage}
 */
export function onMouseup(e) {
  evtEmit.call(this, 'mouseup', e);
}

/**
 * 鼠标双击事件
 * @this {Stage}
 */
export function onDblclick(e) {
  evtEmit.call(this, 'dblclick', e);
  const { selectable } = this;
  if (!selectable) return;
  const { hoverNode, draggableNodes } = this;
  let targetNode = hoverNode;
  // 画板节点 取子节点
  if (['Board'].includes(targetNode.className)) targetNode = targetNode.hoverNode;
  // 编组节点 深度取子节点
  if (['Group'].includes(targetNode.className)) {
    while (targetNode.hoverNode && targetNode !== targetNode.hoverNode) targetNode = targetNode.hoverNode;
  }
  // 编组节点 且 可拖动节点只有此节点 更新是否可深度操作节点状态
  if (targetNode.className === 'Group' && draggableNodes.length === 1 && draggableNodes[0] === targetNode) {
    this.setDepthOperation(!this.depthOperation);
  }
  // 文本节点（不带锚点） 触发文本编辑
  else if (targetNode.className === 'Text' && !targetNode.hasAnchors) {
    this.editTextNode = targetNode;
    const { designerEl, textareaEl } = this;
    designerEl.appendChild(textareaEl);
    textareaEl.focus();
    textareaEl.select();
  }
}

/**
 * 打开上下文菜单事件
 * @this {Stage}
 */
export function onContextmenu(e) {
  evtEmit.call(this, 'contextmenu', e);
  const { dragging, scrolling, nodeDragging, regioning, edging, transformer } = this;
  const { rotating, resizing } = transformer;
  if (dragging || scrolling || nodeDragging || regioning || edging || rotating || resizing) e.preventDefault();
  this.hoveringAnchor = null;
}

/**
 * 滚动鼠标滚轮或类似的输入设备时触发的事件
 * @this {Stage}
 */
export function onWheel(e) {
  evtEmit.call(this, 'wheel', e);
  if (this.scaleable) e.preventDefault();
  // ctrlKey 或 metaKey 为 true（使用触控板进行缩放手势时ctrlKey始终为true）: 缩放
  if (e.ctrlKey || e.metaKey) {
    const delta = e.deltaY || e.deltaX;
    // delta值小于100则判定为触控板，否则鼠标
    const isTouch = Math.abs(delta) < 100;
    const { scale, scaleRatio, offsetCoord } = this;
    let nextScale;
    // delta值大于0：缩小；delta值小于0：放大
    delta > 0 ? (nextScale = scale / scaleRatio) : (nextScale = scale * scaleRatio);
    // 缩放改变量修正
    const change = (nextScale - scale) * (isTouch ? 0.6 : 1);
    // 缩放画布 缩放锚点指向画布相对坐标
    this.setScale(scale + change, offsetCoord);
  }
  // 否则：移动
  else {
    const { pixelRatio } = this;
    const move = { offsetX: -e.deltaX * pixelRatio, offsetY: -e.deltaY * pixelRatio, e };
    // shift 键按下，交换水平垂直方向
    if (e.shiftKey) [move.offsetX, move.offsetY] = [move.offsetY, move.offsetX];
    this.handleMove(move);
  }
  // // 滚动量  缩放时：delta值大于0：缩小；delta值小于0：放大
  // const delta = e.deltaY || e.deltaX;
  // if (delta === 0) return;
  // const { scale, scaleRatio } = this;
  // // 判断是否是触控板触发，delta值小于100则判定为触控板，否则鼠标
  // const isTouch = Math.abs(delta) < 100;
  // const canScale = e.ctrlKey || e.metaKey;
  // let finalScale;
  // // 设备为触控板。
  // if (isTouch) {
  //   // ctrlKey 或 metaKey 为 true（使用触控板进行缩放手势时ctrlKey始终为true）: 缩放
  //   if (canScale) finalScale = scale - (delta / 100) * scale * scaleRatio;
  //   // 否则：移动
  //   else this.handleMove({ offsetX: -e.deltaX, offsetY: -e.deltaY });
  // }
  // // 设备为鼠标
  // else delta > 0 ? (finalScale = this.scale / scaleRatio) : (finalScale = this.scale * scaleRatio);
  // // 缩放画布 缩放锚点指向画布相对坐标
  // if (finalScale) this.setScale(finalScale, this.offsetCoord);
}

/**
 * 键盘按下事件
 * @this {Stage}
 */
export function onKeydown(e) {
  evtEmit.call(this, 'keydown', e);
}

/**
 * 键盘弹起事件
 * @this {Stage}
 */
export function onKeyup(e) {
  evtEmit.call(this, 'keyup', e);
}

/**
 * 滚动条操作开始事件
 * @param {MouseEvent} e
 * @this {Stage}
 */
function onScrollbarStart(e) {
  const { scrolling } = this;
  // 非滚动条操作状态，滚动条操作锚点指向画布相对坐标
  if (!scrolling) this.scrollAnchor = { ...this.offsetCoord, scrollbar: this.hoveringScrollbar };
  this.scrolling = true;
  window.addEventListener('mousemove', this.onScrollbar);
  window.addEventListener('mouseup', this.onScrollbarEnd);
}

/**
 * 滚动条操作事件
 * @this {Stage}
 */
export function onScrollbar(e) {
  // 鼠标未处于按下状态，结束滚动条操作事件
  if (e?.buttons === 0) {
    this.onScrollbarEnd(e);
    return;
  }
  const { offsetCoord, scrollAnchor } = this;
  const { scrollbar } = scrollAnchor;
  // 操作水平滚动条
  if (scrollbar.name === 'horizontal') {
    const offset = offsetCoord.x - scrollAnchor.x;
    // 水平反向移动画布
    this.handleMove({ offsetX: -offset * scrollbar.rate });
    // 水平移动当前操作的滚动条
    scrollbar.x += offset;
  }
  // 操作垂直滚动条
  else if (scrollbar.name === 'vertical') {
    const offset = offsetCoord.y - scrollAnchor.y;
    // 垂直反向移动画布
    this.handleMove({ offsetY: -offset * scrollbar.rate });
    // 垂直移动当前操作的滚动条
    scrollbar.y += offset;
  }
  // 更新滚动条操作锚点坐标为当前画布相对坐标
  Object.assign(scrollAnchor, offsetCoord);
}

/**
 * 滚动条操作结束事件
 * @param {MouseEvent} e
 * @this {Stage}
 */
export function onScrollbarEnd(e) {
  // 鼠标仍然处于按下状态，维持滚动条操作状态
  if (e.buttons > 0) return;
  this.scrolling = false;
  window.removeEventListener('mousemove', this.onScrollbar);
  window.removeEventListener('mouseup', this.onScrollbarEnd);
}

/**
 * 画布拖动开始事件
 * @param {MouseEvent} e
 * @this {Stage}
 */
function onStageDragStart(e) {
  const { dragging, scrolling, nodeDragging, regioning, edging, transformer } = this;
  const { rotating, resizing } = transformer;
  // 非 滚动条操作状态 或 节点拖动中状态 或 框选中状态 或 节点连线中状态 或 转换器旋转，调整大小状态 才可触发画布拖动
  if (scrolling || nodeDragging || regioning || edging || rotating || resizing) return;
  // 非画布拖动中状态，拖动锚点指向画布相对坐标
  if (!dragging) this.dragAnchor = this.offsetCoord;
  this.dragging = true;
  this.setCursor('grabbing');
  window.addEventListener('mousemove', this.onStageDrag);
  window.addEventListener('mouseup', this.onStageDragEnd);
}

/**
 * 画布拖动事件
 * @this {Stage}
 */
export function onStageDrag(e) {
  // 鼠标未处于按下状态，结束画布拖动事件
  if (e?.buttons === 0) {
    this.onStageDragEnd(e);
    return;
  }
  const { offsetCoord, lastOffset, dragAnchor } = this;
  // 计算偏移量 = 上次偏移量 + 画布相对坐标与拖动锚点的差值
  this.offset.x = lastOffset.x + (offsetCoord.x - dragAnchor.x);
  this.offset.y = lastOffset.y + (offsetCoord.y - dragAnchor.y);
  this.emit('drag', this);
  // @ts-ignore 更新文本编辑元素
  this.textareaEl.update();
}

/**
 * 画布拖动结束事件
 * @param {MouseEvent} e
 * @this {Stage}
 */
export function onStageDragEnd(e) {
  // 鼠标仍然处于按下状态，维持拖动状态
  if (e.buttons > 0) return;
  this.dragging = false;
  const { offset } = this;
  // 记录上一次的偏移量
  this.lastOffset.x = offset.x;
  this.lastOffset.y = offset.y;
  window.removeEventListener('mousemove', this.onStageDrag);
  window.removeEventListener('mouseup', this.onStageDragEnd);
}

/**
 * 节点拖动开始事件
 * @param {MouseEvent} e
 * @this {Stage}
 */
export function onNodeDragStart(e) {
  const { draggableNodes } = this;
  // 没有可拖动的节点
  if (!draggableNodes.length) return;
  // 节点拖动锚点指向当前画布网格坐标
  this.nodeDragAnchor = this.gridCoord;
  // 记录拖动开始时的节点位置
  const positionMap = draggableNodes.reduce((map, node) => ((map[node.id] = { x: node.x, y: node.y }), map), {});
  this.nodeDragAnchor.originalPositionMap = positionMap;
  window.addEventListener('mousemove', this.onNodeDrag);
  window.addEventListener('mouseup', this.onNodeDragEnd);
}

/**
 * 节点拖动事件
 * @param {MouseEvent} e
 * @this {Stage}
 */
export function onNodeDrag(e) {
  // 鼠标未处于按下状态，结束节点拖动事件
  if (e?.buttons === 0) {
    this.onNodeDragEnd(e);
    return;
  }
  // 滚动条操作状态 禁止节点拖动
  if (this.scrolling) return;
  const { nodeDragging, nodeDragAnchor, gridCoord, draggableNodes, transformer, stackable } = this;
  if (!nodeDragging) {
    // 节点拖动锚点与当前画布网格坐标相等，说明并未鼠标未移动
    if (nodeDragAnchor.x === gridCoord.x && nodeDragAnchor.y === gridCoord.y) return;
    // 拖动阈值
    if (dist(nodeDragAnchor, gridCoord) <= 1) return;
  }
  // 没有可拖动的节点
  if (!draggableNodes.length) return;
  // 节点有拖动标识
  nodeDragAnchor.dragged = true;
  this.nodeDragging = true;
  // 更新所有可拖动的节点位置
  draggableNodes.forEach((node) => {
    const { x: lastX, y: lastY } = node.getParentCoordByGridCoord(nodeDragAnchor);
    const { x, y } = node.getParentCoordByGridCoord(gridCoord);
    (node.x += x - lastX), (node.y += y - lastY);
  });
  // 更新转换器位置
  if (transformer.visible) {
    const { x: lastX, y: lastY } = nodeDragAnchor;
    const { x, y } = gridCoord;
    transformer.updateOffset({ x: x - lastX, y: y - lastY });
  }
  // 更新节点拖动锚点坐标为当前画布网格坐标
  Object.assign(nodeDragAnchor, gridCoord);
  // 生成辅助线
  generateGuides.call(this);
  // 不可堆叠节点状态
  if (!stackable) {
    // 获取拖动节点与画布可见子节点是否有堆叠
    nodeDragAnchor.stacked = this.allVisibleNodes.some((node1) => {
      if (draggableNodes.includes(node1)) return false; // 忽略拖动节点
      return draggableNodes.some((node2) => isNodeColliding(node1, node2));
    });
    if (nodeDragAnchor.stacked) this.setCursor('not-allowed');
  }
  draggableNodes.forEach((node) => {
    node.updateParent(); // 更新所有实际可拖动的节点父级编组
    node.emit('nodeDrag'); // 发布子节点拖动事件
  });
  this.emit('nodeDrag', draggableNodes);
}

/**
 * 节点拖动结束事件
 * @param {MouseEvent} e
 * @this {Stage}
 */
export function onNodeDragEnd(e) {
  // 鼠标仍然处于按下状态，维持拖动状态
  if (e.buttons > 0) return;
  const { guides, nodeDragAnchor, stackable, transformer } = this;
  this.nodeDragging = false;
  // 清空辅助线
  guides.splice(0);
  // 不可堆叠节点状态 且 拖动节点与画布可见子节点有堆叠
  if (!stackable && nodeDragAnchor.stacked) {
    // 恢复节点到拖动开始时的位置
    this.draggableNodes.forEach((node) => Object.assign(node, nodeDragAnchor.originalPositionMap[node.id]));
    // 更新转换器位置
    if (transformer.visible) transformer.setNodes();
    // 重绘
    else this.draw();
    this.emit('allAction', this);
  }
  // 节点有拖动 记录画布历史状态
  if (nodeDragAnchor.dragged) this._handleRecord();
  window.removeEventListener('mousemove', this.onNodeDrag);
  window.removeEventListener('mouseup', this.onNodeDragEnd);
}

// 框选事件定时器标识
let regionTimer;
/**
 * 框选开始事件
 * @param {MouseEvent} e
 * @this {Stage}
 */
export function onRegionStart(e) {
  if (!this.regionable) return;
  // 框选锚点指向当前画布网格坐标
  this.regionAnchor = this.gridCoord;
  this.regioning = true;
  window.addEventListener('mousemove', this.onRegion);
  window.addEventListener('mouseup', this.onRegionEnd);
  // 创建框选事件定时器
  clearInterval(regionTimer);
  regionTimer = setInterval(() => {
    const { top, bottom, left, right } = this.overflowCoord;
    const move = { offsetX: 0, offsetY: 0 };
    const threshold = 15; // 画布边缘临界阈值
    const moveOffset = 10; // 画布移动偏移量
    if (left < threshold) move.offsetX += moveOffset;
    if (right < threshold) move.offsetX -= moveOffset;
    if (top < threshold) move.offsetY += moveOffset;
    if (bottom < threshold) move.offsetY -= moveOffset;
    // 框选超出画布边缘临界 移动画布
    this.handleMove(move);
  }, 1000 / 60);
}

/**
 * 框选事件
 * @param {MouseEvent} e
 * @this {Stage}
 */
export function onRegion(e) {
  // 鼠标未处于按下状态，结束框选事件
  if (e?.buttons === 0) {
    this.onRegionEnd(e);
    return;
  }
  const { selectedNodes, regionRect } = this;
  // @ts-ignore 更新已选节点
  const { waitRegionNodes } = selectedNodes;
  selectedNodes.length = 0;
  let regionNodes = waitRegionNodes.filter((node) => node.visible && isNodeInRegionRect(node, regionRect));
  // 只框选了一个节点 且 框选节点为画板 框选画板子节点
  if (regionNodes.length === 1 && regionNodes[0].className === 'Board') {
    regionNodes = regionNodes[0].nodes.filter((node) => isNodeInRegionRect(node, regionRect));
  }
  selectedNodes.push(...regionNodes);
}

/**
 * 框选结束事件
 * @param {MouseEvent} e
 * @this {Stage}
 */
export function onRegionEnd(e) {
  // 鼠标仍然处于按下状态，维持框选状态
  if (e.buttons > 0) return;
  this.regioning = false;
  const { draggableNodes, regionRect } = this;
  if (draggableNodes.length) {
    // 更新已选节点为当前可拖动节点
    this.selectedNodes = draggableNodes;
    // 将当前可拖动节点更新到转换器
    this.transformer.setNodes(draggableNodes);
  }
  this.emit('select', this.selectedNodes, 'region');
  this.emit('region', regionRect);
  // 存在已选节点 记录画布历史状态
  if (this.selectedNodes.length) this._handleRecord();
  window.removeEventListener('mousemove', this.onRegion);
  window.removeEventListener('mouseup', this.onRegionEnd);
  // 清除框选事件定时器
  clearInterval(regionTimer);
}

/**
 * 连线开始事件
 * @param {MouseEvent} e
 * @this {Stage}
 */
export function onEdgeStart(e) {
  // 清空已选节点及节点连线
  this.setSelectedNodes([]);
  this.setSelectedEdges([]);
  if (!this.edgeable) return;
  const { hoveringAnchor } = this;
  const edge = { source: hoveringAnchor.node.id, sourceAnchor: hoveringAnchor.id };
  if (this._edgePrecheck('start', edge) === false) return;
  this.emit('edgeStart', edge, this.edges);
  this.tempAnchor = hoveringAnchor;
  this.edging = true;
  window.addEventListener('mousemove', this.onEdge);
  window.addEventListener('mouseup', this.onEdgeEnd);
}

/**
 * 连线事件
 * @param {MouseEvent} e
 * @this {Stage}
 */
export function onEdge(e) {
  // 鼠标未处于按下状态，结束连线事件
  if (e?.buttons === 0) {
    this.onEdgeEnd(e);
    return;
  }
}

/**
 * 连线结束事件
 * @param {MouseEvent} e
 * @this {Stage}
 */
export function onEdgeEnd(e) {
  // 鼠标仍然处于按下状态，维持框选状态
  if (e.buttons > 0) return;
  const { tempEdge } = this;
  this.edging = false;
  this.tempAnchor = null;
  window.removeEventListener('mousemove', this.onEdge);
  window.removeEventListener('mouseup', this.onEdgeEnd);
  if (tempEdge) {
    // 禁止相同锚点连线
    if (tempEdge.source === tempEdge.target && tempEdge.sourceAnchor === tempEdge.targetAnchor) return;
    this.addEdge(tempEdge);
  }
}
